package com.sscl.easyble

import android.bluetooth.le.AdvertiseCallback
import android.bluetooth.le.AdvertiseData
import android.bluetooth.le.AdvertiseSettings
import android.bluetooth.le.BluetoothLeAdvertiser
import android.os.Build
import androidx.annotation.RequiresApi
import com.sscl.easyble.advertise.AdvertiseUuid
import com.sscl.easyble.advertise.FixedAdvertiseData
import com.sscl.easyble.enums.advertise.AdvertiseMode
import com.sscl.easyble.enums.advertise.BleAdvertiseTxPowerLevel
import com.sscl.easyble.interfaces.advertise.BleAdvertiseCallback
import java.util.concurrent.ScheduledExecutorService
import java.util.concurrent.TimeUnit

/**
 * 蓝牙广播发送工具
 */
class BleAdvertiser {


    /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
     *
     * 静态声明
     *
     * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

    companion object {

        /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
         *
         * 属性声明
         *
         * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

        /* * * * * * * * * * * * * * * * * * * 常量属性 * * * * * * * * * * * * * * * * * * */

        private val TAG: String = BleAdvertiser::class.java.simpleName
    }

    /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
     *
     * 属性声明
     *
     * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

    /* * * * * * * * * * * * * * * * * * * 可空属性 * * * * * * * * * * * * * * * * * * */

    /**
     * 超时时间
     */
    private var timeoutTimer: ScheduledExecutorService? = null

    /**
     * 系统API提供的BLE广播器
     */
    private var bluetoothLeAdvertiser: BluetoothLeAdvertiser? = null

    /**
     * 广播回调
     */
    private var bleAdvertiseCallback: BleAdvertiseCallback? = null

    /* * * * * * * * * * * * * * * * * * * 可变属性 * * * * * * * * * * * * * * * * * * */

    /**
     * 是否正在广播
     */
    var isAdvertising: Boolean = false
        private set

    /**
     * 广播模式
     */
    private var advertiseMode = AdvertiseMode.LOW_POWER

    /**
     * 是否为可连接广播
     */
    private var connectable = false

    /**
     * 是否为可发现广播
     */
    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
    private var discoverable = false

    /**
     * 广播功率等级
     */
    private var bleAdvertiseTxPowerLevel = BleAdvertiseTxPowerLevel.ULTRA_LOW

    /**
     * 广播超时时间，0表示永不超时
     */
    private var advertiseTimeoutMillis = 0

    /**
     * 广播数据是否包含信号强度
     */
    private var advertiseDataIncludeTxPowerLevel = false

    /**
     * 广播数据是否包含设备名称
     */
    private var advertiseDataIncludeDeviceName = false

    /**
     * 响应数据是否包含信号强度
     */
    private var scanResponseDataIncludeTxPowerLevel = false

    /**
     * 响应数据是否包含设备名称
     */
    private var scanResponseDataIncludeDeviceName = false

    /* * * * * * * * * * * * * * * * * * * 常量属性 * * * * * * * * * * * * * * * * * * */

    /**
     * 广播超时定时器任务
     */
    private val timeoutTimerRunnable = Runnable {
        InternalConstants.handler.post {
            bleAdvertiseCallback?.timeout()
        }
    }

    /**
     * 要广播的数据列表
     */
    private val fixedAdvertiseDataArrayList = arrayListOf<FixedAdvertiseData>()

    /**
     * 要广播的《UUID及其数据》列表
     */
    private val advertiseUuidArrayList = arrayListOf<AdvertiseUuid>()

    /**
     * 要响应的数据列表
     */
    private val fixedScanResponseDataArrayList = arrayListOf<FixedAdvertiseData>()

    /**
     * 要响应的《UUID及其数据》列表
     */
    private val scanResponseDataUuidArrayList = arrayListOf<AdvertiseUuid>()

    /**
     * 默认的广播回调
     */
    private val defaultAdvertiseCallback = object : AdvertiseCallback() {
        /**
         * Callback triggered in response to [BluetoothLeAdvertiser.startAdvertising] indicating
         * that the advertising has been started successfully.
         *
         * @param settingsInEffect The actual settings used for advertising, which may be different from
         * what has been requested.
         */
        override fun onStartSuccess(settingsInEffect: AdvertiseSettings) {
            super.onStartSuccess(settingsInEffect)
            Logger.log(TAG, "onStartSuccess\nsettingsInEffect:$settingsInEffect")
            val needHandleConnectData =
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
                    settingsInEffect.isConnectable && settingsInEffect.isDiscoverable
                } else {
                    settingsInEffect.isConnectable
                }
            if (needHandleConnectData) {
                //TODO 处理被连接时的数据操作
                Logger.log(TAG, "TODO 处理可被连接时，GATT Server数据操作")
            }
            val timeout = settingsInEffect.timeout
            if (timeout > 0) {
                startTimeoutTimer(timeout)
            }
            bleAdvertiseCallback?.onStartSuccess(settingsInEffect)
        }

        /**
         * Callback when advertising could not be started.
         *
         * @param errorCode Error code (see ADVERTISE_FAILED_* constants) for advertising start
         * failures.
         */
        override fun onStartFailure(errorCode: Int) {
            super.onStartFailure(errorCode)
            isAdvertising = false
            Logger.log(TAG, "onStartFailure")
            when (errorCode) {
                ADVERTISE_FAILED_DATA_TOO_LARGE -> {
                    Logger.log(
                        TAG,
                        "Failed to start advertising as the advertise data to be broadcasted is larger than 31 bytes."
                    )
                }

                ADVERTISE_FAILED_TOO_MANY_ADVERTISERS -> {
                    Logger.log(
                        TAG,
                        "Failed to start advertising because no advertising instance is available."
                    )
                }

                ADVERTISE_FAILED_ALREADY_STARTED -> {
                    Logger.log(
                        TAG,
                        "Failed to start advertising as the advertising is already started"
                    )
                }

                ADVERTISE_FAILED_INTERNAL_ERROR -> {
                    Logger.log(TAG, "Operation failed due to an internal error")
                }

                ADVERTISE_FAILED_FEATURE_UNSUPPORTED -> {
                    Logger.log(TAG, "This feature is not supported on this platform")
                }
            }
            bleAdvertiseCallback?.onStartFailure(errorCode)
        }
    }

    /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
     *
     * 公开方法
     *
     * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

    /**
     * 开始广播
     */
    fun startAdvertise() {
        if (bluetoothLeAdvertiser == null) {
            bluetoothLeAdvertiser = BleManager.bluetoothAdapter?.bluetoothLeAdvertiser
        }
        stopAdvertise()
        bluetoothLeAdvertiser?.startAdvertising(
            initAdvertiseSettings(),
            initAdvertiseData(),
            initScanResponseData(),
            defaultAdvertiseCallback
        )
        isAdvertising = true
    }

    /**
     * 关闭
     */
    fun close() {
        stopAdvertise()
        bluetoothLeAdvertiser = null
        bleAdvertiseCallback = null
    }

    /**
     * 停止广播
     */
    fun stopAdvertise() {
        stopTimeoutTimer()
        bluetoothLeAdvertiser?.stopAdvertising(defaultAdvertiseCallback)
        isAdvertising = false
    }

    /**
     * 设置广播模式
     */
    fun setAdvertiseMode(advertiseMode: AdvertiseMode) {
        this.advertiseMode = advertiseMode
    }

    /**
     * 设置是否可连接
     */
    fun setConnectable(connectable: Boolean) {
        this.connectable = connectable
    }

    /**
     * 设置是否为可发现广播
     */
    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
    fun setDiscoverable(discoverable: Boolean) {
        this.discoverable = discoverable
    }

    /**
     * 设置广播信号强度等级
     */
    fun setAdvertiseTxPowerLevel(bleAdvertiseTxPowerLevel: BleAdvertiseTxPowerLevel) {
        this.bleAdvertiseTxPowerLevel = bleAdvertiseTxPowerLevel
    }

    /**
     * 设置广播超时时间
     */
    fun setAdvertiseTimeoutMillis(advertiseTimeoutMillis: Int) {
        this.advertiseTimeoutMillis = advertiseTimeoutMillis
    }

    /**
     * 设置是否在广播数据中包含信号强度
     */
    fun setAdvertiseDataIncludeTxPowerLevel(advertiseDataIncludeTxPowerLevel: Boolean) {
        this.advertiseDataIncludeTxPowerLevel = advertiseDataIncludeTxPowerLevel
    }

    /**
     * 设置是否在广播数据中包含设备名称
     */
    fun setAdvertiseDataIncludeDeviceName(advertiseDataIncludeDeviceName: Boolean) {
        this.advertiseDataIncludeDeviceName = advertiseDataIncludeDeviceName
    }

    /**
     * 设置是否在响应数据中包含信号强度
     */
    fun setScanResponseDataIncludeTxPowerLevel(scanResponseDataIncludeTxPowerLevel: Boolean) {
        this.scanResponseDataIncludeTxPowerLevel = scanResponseDataIncludeTxPowerLevel
    }

    /**
     * 设置是否在响应数据中包含设备名称
     */
    fun setScanResponseDataIncludeDeviceName(scanResponseDataIncludeDeviceName: Boolean) {
        this.scanResponseDataIncludeDeviceName = scanResponseDataIncludeDeviceName
    }

    /**
     * 添加广播数据
     */
    fun addAdvertiseData(fixedAdvertiseData: FixedAdvertiseData) {
        fixedAdvertiseDataArrayList.add(fixedAdvertiseData)
    }

    /**
     * 移除广播数据
     */
    fun removeAdvertiseData(fixedAdvertiseData: FixedAdvertiseData) {
        fixedAdvertiseDataArrayList.remove(fixedAdvertiseData)
    }

    /**
     * 移除指定位置的广播数据
     */
    fun removeAdvertiseDataAt(index: Int) {
        fixedAdvertiseDataArrayList.removeAt(index)
    }

    /**
     * 清除广播数据
     */
    fun clearAdvertiseData() {
        fixedAdvertiseDataArrayList.clear()
    }

    /**
     *  广播UUID数据
     */
    fun addAdvertiseServiceUuidData(advertiseUuid: AdvertiseUuid) {
        advertiseUuidArrayList.add(advertiseUuid)
    }

    /**
     * 移除广播UUID数据
     */
    fun removeAdvertiseServiceUuidData(advertiseUuid: AdvertiseUuid) {
        advertiseUuidArrayList.remove(advertiseUuid)
    }

    /**
     * 移除指定位置的广播UUID数据
     */
    fun removeAdvertiseServiceUuidDataAt(index: Int) {
        advertiseUuidArrayList.removeAt(index)
    }

    /**
     * 清除广播UUID数据
     */
    fun clearAdvertiseServiceUuidData() {
        advertiseUuidArrayList.clear()
    }

    /**
     * 添加响应数据
     */
    fun addScanResponseData(fixedAdvertiseData: FixedAdvertiseData) {
        fixedScanResponseDataArrayList.add(fixedAdvertiseData)
    }

    /**
     * 移除响应数据
     */
    fun removeScanResponseData(fixedAdvertiseData: FixedAdvertiseData) {
        fixedScanResponseDataArrayList.remove(fixedAdvertiseData)
    }

    /**
     * 移除指定位置的响应数据
     */
    fun removeScanResponseDataAt(index: Int) {
        fixedScanResponseDataArrayList.removeAt(index)
    }

    /**
     * 清除响应数据
     */
    fun clearScanResponseData() {
        fixedScanResponseDataArrayList.clear()
    }

    /**
     * 添加响应数据UUID
     */
    fun addScanResponseServiceUuidData(advertiseUuid: AdvertiseUuid) {
        scanResponseDataUuidArrayList.add(advertiseUuid)
    }

    /**
     * 移除响应数据UUID
     */
    fun removeScanResponseServiceUuidData(advertiseUuid: AdvertiseUuid) {
        scanResponseDataUuidArrayList.remove(advertiseUuid)
    }

    /**
     * 移除指定位置的响应数据UUID
     */
    fun removeScanResponseServiceUuidDataAt(index: Int) {
        scanResponseDataUuidArrayList.removeAt(index)
    }

    /**
     * 清除响应数据UUID
     */
    fun clearScanResponseServiceUuidData() {
        scanResponseDataUuidArrayList.clear()
    }

    /**
     * 设置广播回调
     */
    fun setAdvertiseCallback(advertiseCallback: BleAdvertiseCallback?) {
        this.bleAdvertiseCallback = advertiseCallback
    }

    /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
     *
     * 私有方法
     *
     * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

    /**
     * 初始化广播设置
     */
    private fun initAdvertiseSettings(): AdvertiseSettings {
        return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
            AdvertiseSettings.Builder()
                .setAdvertiseMode(advertiseMode.value)
                .setConnectable(connectable)
                .setTxPowerLevel(bleAdvertiseTxPowerLevel.value)
                .setTimeout(advertiseTimeoutMillis)
                .setDiscoverable(discoverable)
                .build()
        } else {
            AdvertiseSettings.Builder()
                .setAdvertiseMode(advertiseMode.value)
                .setConnectable(connectable)
                .setTxPowerLevel(bleAdvertiseTxPowerLevel.value)
                .setTimeout(advertiseTimeoutMillis)
                .build()
        }
    }

    /**
     * 初始化广播数据
     */
    private fun initAdvertiseData(): AdvertiseData {
        val builder = AdvertiseData.Builder()
        builder.setIncludeTxPowerLevel(advertiseDataIncludeTxPowerLevel)
            .setIncludeDeviceName(advertiseDataIncludeDeviceName)

        for (i in fixedAdvertiseDataArrayList.indices) {
            val fixedAdvertiseData = fixedAdvertiseDataArrayList[i]
            addManufacturerData(builder, fixedAdvertiseData)
        }

        for (i in advertiseUuidArrayList.indices) {
            val advertiseServiceUuid =
                advertiseUuidArrayList[i]
            addServiceData(builder, advertiseServiceUuid)
        }
        return builder.build()
    }

    /**
     * 初始化扫描响应数据
     */
    private fun initScanResponseData(): AdvertiseData? {
        if (fixedScanResponseDataArrayList.isEmpty() && scanResponseDataUuidArrayList.isEmpty()) {
            return null
        }
        val builder = AdvertiseData.Builder()
        builder.setIncludeTxPowerLevel(scanResponseDataIncludeTxPowerLevel)
            .setIncludeDeviceName(scanResponseDataIncludeDeviceName)

        for (i in fixedScanResponseDataArrayList.indices) {
            val fixedAdvertiseData = fixedScanResponseDataArrayList[i]
            addManufacturerData(builder, fixedAdvertiseData)
        }

        for (i in scanResponseDataUuidArrayList.indices) {
            val advertiseServiceUuid =
                scanResponseDataUuidArrayList[i]
            addServiceData(builder, advertiseServiceUuid)
        }
        return builder.build()
    }

    /**
     * add Manufacturer Data to advertise data or scan response data
     * 添加自定义数据到广播数据或扫描响应数据
     *
     * @param builder         AdvertiseData.Builder
     * @param advertiseRecord FixedAdvertiseData
     */
    private fun addManufacturerData(
        builder: AdvertiseData.Builder,
        advertiseRecord: FixedAdvertiseData
    ) {
        val manufacturerId = advertiseRecord.manufactureId
        val data: ByteArray = advertiseRecord.data
        if (manufacturerId == 0) {
            return
        }
        builder.addManufacturerData(manufacturerId, data)
    }

    /**
     * 添加服务数据到广播数据或扫描响应数据
     *
     * @param builder              AdvertiseData.Builder
     * @param advertiseUuid AdvertiseServiceUuid
     */
    private fun addServiceData(
        builder: AdvertiseData.Builder,
        advertiseUuid: AdvertiseUuid
    ) {
        val parcelUuid = advertiseUuid.parcelUuid
        val data = advertiseUuid.data

        if (data == null) {
            builder.addServiceUuid(parcelUuid)
            return
        }
        builder.addServiceUuid(parcelUuid)
        builder.addServiceData(parcelUuid, data)
    }

    /**
     * 开启超时定时器
     */
    private fun startTimeoutTimer(timeout: Int) {
        stopTimeoutTimer()
        timeoutTimer = BleManager.newScheduledExecutorService(1)
        timeoutTimer?.schedule(timeoutTimerRunnable, timeout.toLong(), TimeUnit.MILLISECONDS)
    }

    /**
     * 关闭超时定时器
     */
    private fun stopTimeoutTimer() {
        timeoutTimer?.shutdownNow()
        timeoutTimer = null
    }
}